package com.sec.android.app.myfiles.module.optimizestorage;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteFullException;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.LongSparseArray;
import android.widget.Toast;

import com.samsung.android.sdk.slinkcloud.CloudGatewayMediaStore;
import com.sec.android.app.myfiles.R;
import com.sec.android.app.myfiles.feature.OptimizeStorageMgr;
import com.sec.android.app.myfiles.feature.OptimizeStorageMgr.AnalysisResult;
import com.sec.android.app.myfiles.feature.optimizestorage.DuplicatedFileFinder;
import com.sec.android.app.myfiles.feature.optimizestorage.QueryInfoStore.UnnecessaryFilesGroupInfoQuery;
import com.sec.android.app.myfiles.feature.optimizestorage.QueryInfoStore.UnusedFilesGroupInfoQuery;
import com.sec.android.app.myfiles.info.AppConstants;
import com.sec.android.app.myfiles.info.FileType;
import com.sec.android.app.myfiles.info.MediaFile;
import com.sec.android.app.myfiles.log.Log;
import com.sec.android.app.myfiles.module.abstraction.AbsProviderImp;
import com.sec.android.app.myfiles.module.abstraction.FileRecord;
import com.sec.android.app.myfiles.module.abstraction.FileRecord.OptimizeStorageType;
import com.sec.android.app.myfiles.module.cloud.CloudDbTableInfo;
import com.sec.android.app.myfiles.provider.DbTableInfo;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import static com.sec.android.app.myfiles.feature.optimizestorage.QueryInfoStore.GroupInfo;
import static com.sec.android.app.myfiles.feature.optimizestorage.QueryInfoStore.GroupInfoQuery;
import static com.sec.android.app.myfiles.feature.optimizestorage.QueryInfoStore.LargeFilesGroupInfoQuery;
import static com.sec.android.app.myfiles.module.abstraction.FileRecord.OptimizeStorageType.DuplicateFiles;
import static com.sec.android.app.myfiles.module.abstraction.FileRecord.OptimizeStorageType.LargeFiles;
import static com.sec.android.app.myfiles.module.abstraction.FileRecord.OptimizeStorageType.PotentialSpaceSize;
import static com.sec.android.app.myfiles.module.abstraction.FileRecord.OptimizeStorageType.UnnecessaryData;
import static com.sec.android.app.myfiles.module.abstraction.FileRecord.OptimizeStorageType.UnusedFiles;

/**
 * Created by daesu83.kim on 2016-10-07.
 */

public class OptimizeStorageProviderImp extends AbsProviderImp {

    private static final String KEY_NAME_LENGTH = "nameLength";

    private Map<OptimizeStorageType, GroupInfoQuery> mGroupInfoQueryMap = new HashMap<>();


    public OptimizeStorageProviderImp(Context context) {
        super(context);
        mGroupInfoQueryMap.put(LargeFiles, new LargeFilesGroupInfoQuery());
        mGroupInfoQueryMap.put(UnusedFiles, new UnusedFilesGroupInfoQuery());
        mGroupInfoQueryMap.put(UnnecessaryData, new UnnecessaryFilesGroupInfoQuery());
    }

    @Override
    protected FileRecord.StorageType getStorage() {
        return FileRecord.StorageType.OptimizeStorage;
    }

    @Override
    protected boolean needRefresh(Uri uri, FileRecord curRecord) {
        return false;
    }

    @Override
    protected void _refreshDB(Uri uri, FileRecord curRecord) {
    }

    @Override
    protected Cursor _query(Uri uri, String[] projectionIn, String selection, String[] selectionArgs, String sortOrder) {
        Cursor ret = null;
        String paramType = uri.getQueryParameter(OptimizeStorageDbTableInfo.QUERY_PARAM_OPTIMIZE_STORAGE_TYPE);
        String paramRefresh = uri.getQueryParameter(OptimizeStorageDbTableInfo.QUERY_PARAM_REFRESH);
        String paramIsFilterAll = uri.getQueryParameter(OptimizeStorageDbTableInfo.QUERY_PARAM_IS_FILTER_ALL);
        Log.d(this, "Optimize storage query start : " + paramType + ", " + paramRefresh);
        long start = System.currentTimeMillis();
        try {
            if (!TextUtils.isEmpty(paramType)) {
                OptimizeStorageType type = OptimizeStorageType.valueOf(paramType);
                if (type == PotentialSpaceSize) {
                    ret = getPotentialSpaceSizeCursor();
                } else {
                    boolean refresh = Boolean.parseBoolean(paramRefresh);
                    boolean isFilterAll = Boolean.parseBoolean(paramIsFilterAll);
                    final String OPTIMIZE_TYPE_SELECTION = mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.OPTIMIZE_TYPE) +
                            "='" + paramType + "'";
                    selection = concatSelection(OPTIMIZE_TYPE_SELECTION, selection);
                    if (refresh) {
                        synchronized (OptimizeStorageProviderImp.class) {
                            super._delete(OPTIMIZE_TYPE_SELECTION, null);
                            List<AnalysisResult> rawResults = OptimizeStorageMgr.getInstance(mContext).getAnalysisResult(type);
                            if (rawResults != null) {
                                insertRawResults(type, rawResults);
                            }
                        }
                    }
                    ret = getResultCursor(type, uri, projectionIn, selection, selectionArgs, sortOrder, isFilterAll);
                }
            } else {
                Log.d(this, "no optimize type");
                ret = super._query(uri, projectionIn, selection, selectionArgs, sortOrder);
            }
            Log.d(this, "Optimize storage query finished : " + paramType + ", elapsed time : " + (System.currentTimeMillis() - start));
        } catch (SQLiteFullException e) {
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(mContext, R.string.not_enough_memory_popup, Toast.LENGTH_LONG).show();
                }
            });
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
            ret = null;
        }
        return ret;
    }

    private Cursor getPotentialSpaceSizeCursor() {
        String sizeColumnName = mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.SIZE);
        String storageTypeColumnName = mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.STORAGE_TYPE);
        String pathColumnName = mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.PATH);
        String optimizeTypeColumnName = mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.OPTIMIZE_TYPE);
        String subGroupColumnName = mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.SUB_GROUP_ID);
        String local = FileRecord.StorageType.Local.toString();
        String duplicatedFiles = DuplicateFiles.toString();

        String targetSelection = String.format(Locale.getDefault(),
                "(%s='%s' AND %s!='%s') OR (%s='%s' AND %s='%s' AND %s!=-1)",
                storageTypeColumnName, local, optimizeTypeColumnName, duplicatedFiles,
                storageTypeColumnName, local, optimizeTypeColumnName, duplicatedFiles, subGroupColumnName);
        String query = String.format(Locale.getDefault(),
                "select sum(%s) from (select %s from %s where %s group by %s);",
                sizeColumnName, sizeColumnName, mDbTableInfo.getTableName(),
                targetSelection, pathColumnName);
        return mDB.rawQuery(query, null);
    }

    private Cursor getResultCursor(OptimizeStorageType type, Uri uri, String[] projectionIn,
                                   String selection, String[] selectionArgs, String sortOrder,
                                   boolean isRefresh) {
        Cursor ret = null;
        if (type == DuplicateFiles) {
            DuplicatedFileFinder finder = new DuplicatedFileFinder(mContext);
            selection = selection + " AND " + mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.SUB_GROUP_ID) + " != -1";
            AnalysisResult result = finder.getDuplicatedFiles(OptimizeStorageMgr.StorageDbType.OPTIMIZE_STORAGE,
                    selection, selectionArgs, null, isRefresh);
            if (result != null) {
                ArrayList<String> groupNames = getDuplicatedFilesGroupNames(selection, selectionArgs);
                result.mCursor.getExtras().putStringArrayList(OptimizeStorageDbTableInfo.EXTRAS_KEY_GROUP_NAME,
                        groupNames);
                ret = result.mCursor;
            }
        } else {
            if (type == UnnecessaryData) {
                String tableName = mDbTableInfo.getTableName();
                String extCol = mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.EXT);
                String sizeCol = mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.SIZE);
                String nameCol = mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.NAME);
                StringBuilder query = new StringBuilder();
                query.append("select * from ").append(tableName).append(" where ").append(selection)
                        .append(" and ").append(extCol).append("='log'")
                        .append(" union all ")
                        .append("select * from ").append(tableName).append(" where ").append(selection)
                        .append(" and ").append(extCol).append("='tmp'")
                        .append(" union all ")
                        .append("select * from ").append(tableName).append(" where ").append(selection)
                        .append(" and ").append(extCol).append("!='log' and ").append(extCol).append("!='tmp' and ")
                        .append(nameCol).append("!='.nomedia' and ").append(sizeCol).append("=0");
                ret = mDB.rawQuery(query.toString(), selectionArgs);
            } else {
                ret = super._query(uri, projectionIn, selection, selectionArgs, sortOrder);
            }
            if (ret != null && ret.getCount() > 0) {
                Bundle extras = getGroupInfo(type, selection, selectionArgs);
                long totalSize = getTotalSize(uri, selection, selectionArgs);
                if (totalSize > 0) {
                    extras.putLong(OptimizeStorageDbTableInfo.EXTRAS_KEY_TOTAL_SIZE, totalSize);
                }
                if (!extras.isEmpty()) {
                    ret.setExtras(extras);
                }
            }
        }
        return ret;
    }

    private ArrayList<String> getDuplicatedFilesGroupNames(String selection, String[] selectionArgs) {
        ArrayList<String> groupNames = new ArrayList<>();
        String nameColumnName = mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.NAME);
        String sizeColumnName = mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.SIZE);
        String subGroupIdColumnName = mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.SUB_GROUP_ID);
        try (Cursor c = mDB.query(mDbTableInfo.getTableName(),
                new String[]{nameColumnName},
                mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.OPTIMIZE_TYPE) + "='" +
                        DuplicateFiles.toString() + "' AND " + selection,
                selectionArgs,
                mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.SUB_GROUP_ID),
                "LENGTH(" + nameColumnName + ")=MIN(LENGTH(" + nameColumnName + "))",
                sizeColumnName + " desc, " + subGroupIdColumnName + " asc, " + nameColumnName + " asc")) {
            if (c != null && c.moveToFirst()) {
                do {
                    groupNames.add(c.getString(0));
                } while (c.moveToNext());
            }
        }
        return groupNames;
    }

    /**
     * full query
     * <p/>
     * select t.range as [group name], count(*) as [child count]
     * from (
     * the query to get all section in each optimize storage type
     * ) t
     * group by t.range order by t.range asc
     */
    private Bundle getGroupInfo(OptimizeStorageType type, String selection, String[] selectionArgs) {
        Bundle ret = new Bundle();
        GroupInfoQuery giq = mGroupInfoQueryMap.get(type);
        if (giq != null) {
            StringBuilder query = new StringBuilder();
            query.append("select t.range as [group name], count(*) as [child count] ")
                    .append("from (")
                    .append(giq.getRangeQuery(mContext, selection))
                    .append(") t");
            query.append(" group by t.range order by t.range asc");

            try (Cursor c = mDB.rawQuery(query.toString(), selectionArgs)) {
                GroupInfo groupInfo = giq.getGroupInfo(mContext, c);
                if (groupInfo != null) {
                    ret.putStringArrayList(OptimizeStorageDbTableInfo.EXTRAS_KEY_GROUP_NAME,
                            groupInfo.mGroupNames);
                    ret.putIntArray(OptimizeStorageDbTableInfo.EXTRAS_KEY_GROUP_CHILD_COUNT,
                            groupInfo.mGroupChildCount);
                }
            }
        }
        return ret;
    }

    private long getTotalSize(Uri uri, String selection, String[] selectionArgs) {
        long ret = 0L;
        Cursor c = super._query(uri,
                new String[]{"SUM(" + mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.SIZE) + ')'},
                selection, selectionArgs, null);
        if (c != null) {
            if (c.moveToFirst()) {
                ret = c.getLong(0);
            }
            c.close();
        }
        return ret;
    }

    private void insertRawResults(OptimizeStorageType type, List<AnalysisResult> rawResults) {
        if (rawResults != null) {
            Uri uri = Uri.parse(mDbTableInfo.getUri());
            if (type == DuplicateFiles) {
                _bulkInsert(uri, getDuplicatedFilesContentValues(rawResults));
            } else {
                if (rawResults != null) {
                    for (AnalysisResult rawResult : rawResults) {
                        _bulkInsert(uri, getContentValues(rawResult));
                    }
                }
            }
        }
    }

    private ContentValues[] getDuplicatedFilesContentValues(List<AnalysisResult> rawResults) {
        List<ContentValues> ret = new ArrayList<>();
        if (rawResults != null) {
            LongSparseArray<List<SubGroupInfo>> sizeToSubGroupListMap = new LongSparseArray<>();
            ContentValues values;
            String fileName;
            String fileExt;
            for (AnalysisResult rawResult : rawResults) {
                try (Cursor rawResultCursor = rawResult.mCursor) {
                    OptimizeStorageType rawResultType = rawResult.mType;
                    if (rawResultCursor != null) {
                        int count = rawResultCursor.getCount();
                        if (count > 0 && rawResultCursor.moveToFirst()) {
                            Log.d(this, "getDuplicatedFilesContentValues - count:" + count);
                            ColumnIndices indices = getColumnIndex(rawResult.mStorageDbType, rawResultCursor);
                            do {
                                values = getValuesFromCursor(rawResult.mType, rawResult.mStorageDbType, indices, rawResultCursor);
                                values.put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.OPTIMIZE_TYPE), rawResultType.toString());
                                fileName = values.getAsString(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.NAME));
                                fileExt = values.getAsString(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.EXT));
                                values.put(KEY_NAME_LENGTH, getFileNameLength(fileName, fileExt));
                                ret.add(values);
                            } while (rawResultCursor.moveToNext());
                        }
                    }
                }
            }
            if (!ret.isEmpty()) {
                Collections.sort(ret, new Comparator<ContentValues>() {
                    @Override
                    public int compare(ContentValues o1, ContentValues o2) {
                        int ret;
                        Integer length1 = (o1 == null) ? 0 : o1.getAsInteger(KEY_NAME_LENGTH);
                        Integer length2 = (o2 == null) ? 0 : o2.getAsInteger(KEY_NAME_LENGTH);
                        if (length1 == null || length2 == null) {
                            ret = 0;
                        } else {
                            ret = length1 - length2;
                        }
                        return ret;
                    }
                });

                for (ContentValues v : ret) {
                    updateSubGroupInfo(sizeToSubGroupListMap, v);
                    v.remove(KEY_NAME_LENGTH);
                }
            }
            ensureSubGroupId(sizeToSubGroupListMap);
        }
        return ret.toArray(new ContentValues[ret.size()]);
    }

    private int getFileNameLength(String fileName, String extension) {
        int fileNameLength = fileName != null ? fileName.length() : 0;
        int ret = fileNameLength - (TextUtils.isEmpty(extension) ? 0 : extension.length() + 1/*dot*/);
        return ret < 0 ? fileNameLength : ret;
    }

    private void updateSubGroupInfo(LongSparseArray<List<SubGroupInfo>> sizeToSubGroupListMap, ContentValues values) {
        String fileName = values.getAsString(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.NAME));
        Long fileSize = values.getAsLong(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.SIZE));
        String fileExtension = values.getAsString(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.EXT));
        if (fileSize != null) {
            List<SubGroupInfo> subGroupInfos = sizeToSubGroupListMap.get(fileSize);
            boolean needNewSubGroupInfo = true;
            if (subGroupInfos == null) {
                subGroupInfos = new ArrayList<>();
                sizeToSubGroupListMap.put(fileSize, subGroupInfos);
            } else {
                for (SubGroupInfo info : subGroupInfos) {
                    if (isSameGroup(info.mKeyFileName, info.mKeyFileExtension, fileName, fileExtension)) {
                        info.mValuesList.add(values);
                        needNewSubGroupInfo = false;
                        break;
                    }
                }
            }
            if (needNewSubGroupInfo) {
                SubGroupInfo subGroupInfo = new SubGroupInfo();
                subGroupInfo.mSubGroupSize = fileSize;
                subGroupInfo.mKeyFileName = fileName;
                subGroupInfo.mKeyFileExtension = fileExtension;
                subGroupInfo.mValuesList.add(values);
                subGroupInfos.add(subGroupInfo);
            }
        } else {
            Log.e(this, "file size is null in DB");
        }
    }

    private boolean isSameGroup(String srcFileName, String srcFileExtension,
                                String targetFileName, String targetFileExtension) {
        boolean ret = false;
        boolean sameExtension;
        if (TextUtils.isEmpty(srcFileExtension)) {
            sameExtension = TextUtils.isEmpty(targetFileExtension);
        } else {
            sameExtension = srcFileExtension.equalsIgnoreCase(targetFileExtension);
        }
        try {
            if (sameExtension) {
                int srcFileNameLength = getFileNameLength(srcFileName, srcFileExtension);
                String srcFileNameWithoutExt = srcFileName.substring(0, srcFileNameLength);

                int targetFileNameLength = getFileNameLength(targetFileName, targetFileExtension);
                String targetFileNameWithoutExt = targetFileName.substring(0, targetFileNameLength);

                ret = srcFileNameLength > targetFileNameLength ?
                        srcFileNameWithoutExt.startsWith(targetFileNameWithoutExt) :
                        targetFileNameWithoutExt.startsWith(srcFileNameWithoutExt);
            }
        } catch (StringIndexOutOfBoundsException e) {
            Log.d(this, "msg : " + e.getMessage());
            Log.d(this, "srcFileName : " + srcFileName + " srcFileExtension : " + srcFileExtension
                    + " targetFileName : " + targetFileName + " targetFileExtension ");
        }
        return ret;
    }

    private void ensureSubGroupId(LongSparseArray<List<SubGroupInfo>> sizeToSubGroupMap) {
        int subGroupMapSize = sizeToSubGroupMap.size();
        List<Long> sortedKeyList = new ArrayList<>(subGroupMapSize);
        for (int i = 0; i < subGroupMapSize; i++) {
            sortedKeyList.add(sizeToSubGroupMap.keyAt(i));
        }
        Collections.sort(sortedKeyList);

        int keyCount = sortedKeyList.size();
        int subGroupIdGenerator = 0;
        for (int i = keyCount - 1; i >= 0; i--) {
            List<SubGroupInfo> subGroupInfos = sizeToSubGroupMap.get(sortedKeyList.get(i));
            if (subGroupInfos != null) {
                if (subGroupInfos.size() > 1) {
                    Collections.sort(subGroupInfos, new Comparator<SubGroupInfo>() {
                        @Override
                        public int compare(SubGroupInfo o1, SubGroupInfo o2) {
                            return o2.mKeyFileName.compareToIgnoreCase(o1.mKeyFileName);
                        }
                    });
                }
                SubGroupInfo info;
                Iterator<SubGroupInfo> it = subGroupInfos.iterator();
                while (it.hasNext()) {
                    info = it.next();
                    int subGroupChildCount = info.mValuesList.size();
                    if (subGroupChildCount == 1) {
                        info.mValuesList.get(0).put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.SUB_GROUP_ID), -1);
                    } else {
                        for (ContentValues values : info.mValuesList) {
                            values.put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.SUB_GROUP_ID), subGroupIdGenerator);
                        }
                        subGroupIdGenerator++;
                    }
                }
            }
        }
    }

    private ContentValues[] getContentValues(AnalysisResult rawResult) {
        ContentValues[] ret = null;
        int index = 0;
        try (Cursor c = rawResult.mCursor) {
            OptimizeStorageType type = rawResult.mType;
            if (c != null) {
                int count = c.getCount();
                if (count > 0 && c.moveToFirst()) {
                    ret = new ContentValues[count];
                    ColumnIndices indices = getColumnIndex(rawResult.mStorageDbType, c);
                    do {
                        ret[index] = getValuesFromCursor(rawResult.mType, rawResult.mStorageDbType, indices, c);
                        ret[index].put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.OPTIMIZE_TYPE), type.toString());
                        index++;
                    } while (c.moveToNext());
                }
            }
        }
        return ret;
    }

    private ColumnIndices getColumnIndex(OptimizeStorageMgr.StorageDbType storageDbType, Cursor c) {
        ColumnIndices indices = new ColumnIndices();
        switch (storageDbType) {
            case MEDIA_PROVIDER:
            case WRAP_MEDIA_PROVIDER:
                indices.mIdIndex = c.getColumnIndex(MediaStore.MediaColumns._ID);
                indices.mPathIndex = c.getColumnIndex(MediaStore.MediaColumns.DATA);
                indices.mNameIndex = c.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
                indices.mDateIndex = c.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED);
                indices.mSizeIndex = c.getColumnIndex(MediaStore.MediaColumns.SIZE);
                indices.mFileIdIndex = -1;
                indices.mParentIdIndex = -1;
                indices.mDeviceIdIndex = -1;
                indices.mDateAccessedIndex = c.getColumnIndex(AppConstants.MEDIA_STORE_FILE_COLUMNS_DATE_ACCESSED);
                break;

            case CLOUD_GOOGLE_DRIVE:
            case CLOUD_SAMSUNG_DRIVE:
                indices.mIdIndex = c.getColumnIndex(CloudGatewayMediaStore.Files.FileColumns._ID);
                indices.mPathIndex = c.getColumnIndex(CloudGatewayMediaStore.Files.FileColumns.DATA);
                indices.mNameIndex = c.getColumnIndex(CloudGatewayMediaStore.Files.FileColumns.DISPLAY_NAME);
                indices.mDateIndex = c.getColumnIndex(CloudGatewayMediaStore.Files.FileColumns.DATE_MODIFIED);
                indices.mSizeIndex = c.getColumnIndex(CloudGatewayMediaStore.Files.FileColumns.SIZE);
                indices.mFileIdIndex = c.getColumnIndex(CloudDbTableInfo.COLUMN_NAME_FILE_ID);
                indices.mParentIdIndex = c.getColumnIndex(CloudDbTableInfo.COLUMN_NAME_PARENT_ID);
                indices.mDeviceIdIndex = c.getColumnIndex(CloudGatewayMediaStore.Files.FileColumns.DEVICE_ID);
                indices.mDateAccessedIndex = -1;
                break;
        }
        return indices;
    }

    private ContentValues getValuesFromCursor(OptimizeStorageType optimizeStorageType,
                                              OptimizeStorageMgr.StorageDbType storageDbType,
                                              ColumnIndices indices, Cursor rawResult) {
        ContentValues ret = new ContentValues();

        String fullPath = rawResult.getString(indices.mPathIndex);
        FileRecord.StorageType storageType = FileRecord.StorageType.Local;

        if (storageDbType.isCloud()) {
            storageType = FileRecord.StorageType.Cloud;
            ret.put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.DEVICE_ID), rawResult.getInt(indices.mDeviceIdIndex));
        }

        ret.put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.MEDIA_ID), rawResult.getInt(indices.mIdIndex));
        ret.put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.STORAGE_TYPE), storageType.toString());
        ret.put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.PATH), fullPath);
        ret.put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.NAME), rawResult.getString(indices.mNameIndex));
        ret.put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.SIZE), getFileSize(optimizeStorageType, storageDbType, indices, rawResult));
        ret.put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.DATE), rawResult.getLong(indices.mDateIndex));
        ret.put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.FILE_TYPE), MediaFile.getFileType(fullPath, false));
        ret.put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.EXT), FileRecord.getExt(fullPath));
        if (indices.mFileIdIndex != -1) {
            ret.put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.FILE_ID), rawResult.getString(indices.mFileIdIndex));
        }
        if (indices.mParentIdIndex != -1) {
            ret.put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.PARENT_CLOUD_ID), rawResult.getString(indices.mParentIdIndex));
        }
        if (indices.mDateAccessedIndex != -1) {
            ret.put(mDbTableInfo.getColumnName(DbTableInfo.COLUMN_ID.DATE_ACCESSED), rawResult.getLong(indices.mDateAccessedIndex));
        }
        return ret;
    }

    private long getFileSize(OptimizeStorageType optimizeStorageType,
                                OptimizeStorageMgr.StorageDbType storageDbType,
                                ColumnIndices indices, Cursor rawResult) {
        long size = rawResult.getLong(indices.mSizeIndex);
        String fullPath = rawResult.getString(indices.mPathIndex);
        int fileType = MediaFile.getFileType(fullPath, false);
        if (size == 0L &&
                optimizeStorageType == OptimizeStorageType.UnnecessaryData &&
                !storageDbType.isCloud() &&
                (FileType.isImageFileType(fileType) ||
                FileType.isAudioFileType(fileType) ||
                FileType.isVideoFileType(fileType))) {
            File file = new File(fullPath);
            long realSize = file.length();
            if (realSize != size) {
                size = realSize;
            }
        }
        return size;
    }

    private String concatSelection(String... selections) {
        StringBuilder sb = new StringBuilder();
        if (selections != null && selections.length > 0) {
            boolean isFirst = true;
            for (String selection : selections) {
                if (!TextUtils.isEmpty(selection)) {
                    if (isFirst) {
                        sb.append('(').append(selection).append(')');
                        isFirst = false;
                    } else {
                        sb.append(" AND (").append(selection).append(')');
                    }
                }
            }
        }
        return sb.toString();
    }


    private static class ColumnIndices {
        public int mIdIndex;
        public int mPathIndex;
        public int mNameIndex;
        public int mSizeIndex;
        public int mDateIndex;
        public int mFileIdIndex;
        public int mParentIdIndex;
        public int mDeviceIdIndex;
        public int mDateAccessedIndex;
    }

    private static class SubGroupInfo {
        public long mSubGroupSize;
        public String mKeyFileName;
        public String mKeyFileExtension;
        public List<ContentValues> mValuesList = new ArrayList<>();
    }
}
